Frontend Forever App
We have a mobile app for you to download and use. And you can unlock many features in the app.
Get it now
Intall Later
Run
HTML
CSS
Javascript
Output
Document
@charset "UTF-8"; @import url(https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800); *, :after, :before { box-sizing: border-box; padding: 0; margin: 0; } @layer flippable { .flippable_root[data-flip] { transition: transform 0.4s cubic-bezier(0.2, 0, 0.5, 1); transform: none; transform-origin: top left; } .flippable_root[data-flip="invert"] { transition: none !important; transform: var(--flip-translate) var(--flip-scale); --flip-translate: translate(calc(-1px * var(--dx)), calc(-1px * var(--dy))); --flip-scale: scale(calc(1 / var(--dw)), calc(1 / var(--dh))); --flip-radius: calc((var(--dw)) * var(--radius)) / calc((var(--dh)) * var(--radius)); } } /* ---------------------------------- */ :root { --color-1: #c9dabf; --color-2: #9ca986; --color-3: #808d7c; --color-4: #5f6f65; background: var(--color-1); color: var(--color-4); font-family: monospace; margin: 0; padding: 0; } body { margin: 0; padding: 0; } .grid-container { display: grid; place-items: center; place-content: center; height: 100dvh; } .shuffle-button { background: var(--color-2); padding: 0.5em 1em; font-family: inherit; font-size: 3vmin; border: 1vmin outset var(--color-4); color: var(--color-4); cursor: pointer; &:active { background: var(--color-3); transform: translateY(2px); } } .grid { margin: 1vmin; display: grid; grid-template-rows: repeat(var(--rows), 1fr); grid-template-columns: repeat(var(--cols), 1fr); border: 1vmin outset var(--color-2); } .grid-item { display: grid; aspect-ratio: 1 / 1; } .grid-item-flip { border: 1vmin inset var(--color-2); display: grid; aspect-ratio: 1 / 1; place-items: center; color: var(--color-3); font-size: 2.5vmin; padding: 1.25em; background: var(--color-1); cursor: pointer; &:active { background: var(--color-2); } } .grid-item-flip[data-selected="true"] { transition-delay: 0s; transition-duration: 0.4s; mix-blend-mode: darken; transition-timing-function: cubic-bezier(0.5, 0, 0.2, 1); background: var(--color-4); color: var(--color-2); position: relative; z-index: 2; }
console.log("Event Fired") import React from "https://esm.sh/react"; import ReactDOM from "https://esm.sh/react-dom/client"; import { flushSync } from "https://esm.sh/react-dom"; console.clear(); const styles = { root: "flippable_root" }; function Flippable({ className, component: Component = "div", flipId, ...props }) { const ref = useFlippableRef(flipId); const classes = [styles.root, className].filter(Boolean).join(" "); return
; } const FLIP_TRANSITION_EVENT = "flip-transition"; const FLIP_TRANSITION_END_EVENT = "flip-transition-end"; /** For bigger DOM changes, like a full list transition, call `startFlipTransition` first to ensure the positions all get properly indexed. */ function startFlipTransition(callback, flipId) { document.dispatchEvent( new CustomEvent(FLIP_TRANSITION_EVENT, { detail: flipId }) ); requestAnimationFrame(async () => { await callback?.(); requestAnimationFrame(() => { document.dispatchEvent( new CustomEvent(FLIP_TRANSITION_END_EVENT, { detail: flipId }) ); }); }); } const rects = {}; function useFlippableRef(flipIdProp) { const ref = React.useRef(); const flipIdRef = React.useRef(flipIdProp); flipIdRef.current = flipIdProp; const updateOnExit = React.useRef(true); React.useLayoutEffect(() => { const el = ref.current; const flipId = flipIdRef.current; if (!el || !flipId) return; const updateRect = (e) => { if (el.parentNode && (!e?.detail || e?.detail === flipId)) { updateOnExit.current = false; rects[flipId] = el.getBoundingClientRect(); } }; function clearUpdateValue() { updateOnExit.current = true; } document.addEventListener(FLIP_TRANSITION_EVENT, updateRect); document.addEventListener(FLIP_TRANSITION_END_EVENT, clearUpdateValue); const oldRect = rects[flipId]; delete rects[flipId]; const currentRect = el?.getBoundingClientRect(); if (oldRect && currentRect) { el.style.setProperty("--dx", Math.round(currentRect.x - oldRect.x)); el.style.setProperty("--dy", Math.round(currentRect.y - oldRect.y)); el.style.setProperty("--dw", currentRect.width / oldRect.width); el.style.setProperty("--dh", currentRect.height / oldRect.height); el.classList.add(styles.root); el.dataset.flip = "invert"; requestAnimationFrame(() => { el.dataset.flip = "play"; }); } return () => { if (el.parentNode && updateOnExit.current) { rects[flipId] = el?.getBoundingClientRect(); } document.removeEventListener(FLIP_TRANSITION_EVENT, updateRect); document.removeEventListener(FLIP_TRANSITION_END_EVENT, clearUpdateValue); }; }, []); return ref; } /* ---------------------------------- */ const root = ReactDOM.createRoot(document.querySelector("#app")); root.render(
); const mainId = "F.L.I.P."; function GridExample() { const [cols, setCols] = React.useState([ mainId, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ]); const [pos, setPos] = React.useState(0); function moveTo(id) { startFlipTransition(() => { setCols((cols) => { const newCols = [...cols]; const itemIndex = newCols.indexOf(id); const mainIndex = newCols.indexOf(mainId); newCols[itemIndex] = mainId; newCols[mainIndex] = id; return newCols; }); }, id); } React.useLayoutEffect(() => { setTimeout(() => startFlipTransition(() => setPos(6)), 100); }, []); return (
{cols.map((id, i) => { return (
moveTo(id)} > {id}
); })}
{ startFlipTransition(() => { setCols((cols) => shuffle(cols)); }); }} > Shuffle
); } function shuffle(array) { return array .map((value) => ({ value, sort: Math.random() })) .sort((a, b) => a.sort - b.sort) .map(({ value }) => value); }